package implementation1.app.commands;

import exceptions.*;
import implementation1.app.Analyzer;
import implementation1.app.Global;
import implementation1.app.streams.AccessAttributes;
import implementation1.app.streams.StdStream;
import implementation1.data.Handle;
import implementation1.data.states.RunningStatus;
import implementation1.data.states.WaitingStatus;
import implementation1.gui.ShellWindow;
import interfaces.app.IAnalyzer;
import interfaces.app.IProcess;
import interfaces.app.IStream;
import interfaces.data.IHandle;
import interfaces.gui.IShellWindow;

import javax.swing.*;
import java.awt.*;

/**
 * Class for handle command "shell" for creating shell for new login user.
 *
 * @author Jan Svab
 *         Created 15.10.11
 */
public class Shell extends Command
{
    /**
     * Analyzer for incomming commands.
     */
    private final IAnalyzer analyzer = new Analyzer();

    /**
     * Default constructor.
     *
     * @param parent parent process over which is command processed
     * @param handle parameters of command
     */
    public Shell(IProcess parent, IHandle handle)
    {
        super(parent, handle);
    }

    /**
     * Main method for processing of command.
     *
     * @param args arguments of command
     */
    @Override
    public void mainCommand(String[] args) throws CommandException
    {
        if (args != null && args.length != 0)
        {
            throw new CommandErrorFromUserException(bundle.getString("error.arguments.wrongCount"));
        }

        //creating and opening std stream with ShellWindow
        final IShellWindow sh = new ShellWindow(handle);
        IStream stdin = new StdStream(sh);
        IStream stdout = new StdStream(sh);
        IStream stderrout = new StdStream(sh);

        handle = new Handle(handle.getParams(), stdin, stdout, stderrout, handle.getCommandName(), handle.getActualPath(), handle.getAccount());

        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                ((JFrame) sh).setVisible(true);
            }
        });

        //reading of command from user
        while (!hasEndStatus())
        {
            String inputStr = null;
            try
            {
                this.status = new WaitingStatus();
                ((ShellWindow) sh).setPrompt(handle.getActualPath()); //updating of actual directory

                stdin.open(AccessAttributes.READ);
                openedStreamsOfProcess.add(stdin);

                inputStr = stdin.readLine();  //blocking operation

                //during waiting on next command from user, process could end on request from another process.
                if (hasEndStatus())
                {
                    break;
                }

                this.status = new RunningStatus();

            } catch (StreamEOFException e) //e.g. CTRL+D in ShellWindow, closing ShellWindow
            {
                break;
            } catch (StreamException e)
            {
                throw new CommandException(e.getMessage());
            } finally
            {
                Global.closeStreamQuietly(stdin);
                openedStreamsOfProcess.remove(stdin);
            }

            //empty command
            if (inputStr.equals(""))
            {
                continue;
            }

            //processing of new command
            IHandle handleForCmd = callAnalyzer(inputStr);

            //creating of the process for new command
            try
            {
                IProcess child = createChildProcess(handleForCmd);
                ((ShellWindow) sh).setPrompt(handleForCmd.getCommandName());
                child.startProcess();
                if (!(child instanceof Shell)) //if new process isn't shell, parent process muss wait on child
                {
                    waitToChild(child.getPid()); //waiting on end of new child process
                }

                //during waiting on next command from user, process could end on request from another process.
                if (hasEndStatus())
                {
                    break;
                }

            } catch (CreatingProcessException e)
            {
                if (handle.getErrorOut() != null)
                {
                    try
                    {
                        handle.getErrorOut().open(AccessAttributes.WRITE);
                        openedStreamsOfProcess.add(handle.getErrorOut());

                        handle.getErrorOut().writeLine(e.getMessage());

                    } catch (StreamException e1)
                    {
                        throw new CommandException(e1.getMessage());
                    } finally
                    {
                        Global.closeStreamQuietly(handle.getErrorOut());
                        openedStreamsOfProcess.remove(handle.getErrorOut());
                    }
                }
            }
        }

        ((ShellWindow) sh).close();
    }

    /**
     * @param line a string with the command
     * @return a handle for creating proces for the command
     */
    private IHandle callAnalyzer(String line)
    {
        analyzer.setCommand(line);
        analyzer.setBasePath(handle.getActualPath());
        String name = analyzer.getCommandName();
        String[] arg = analyzer.getArguments();

        try
        {
            IStream in = analyzer.getInput() != null ? analyzer.getInput() : handle.getIn();

            IStream out = analyzer.getOutput() != null ? analyzer.getOutput() : handle.getOut();
            IStream err = analyzer.getErrorOutput() != null ? analyzer.getErrorOutput() : handle.getErrorOut();
            return new Handle(arg, in, out, err, name, handle.getActualPath(), handle.getAccount());
        } catch (StreamException e)
        {
            return new Handle(arg, null, null, null, name, handle.getActualPath(), handle.getAccount());
        }
    }

    @Override
    public String toString()
    {
        return "shell";
    }
}
